#!/usr/bin/env python
"""
==============================
resource_packer documentation
==============================
Introduction
============================
This package provide a simple way to embed external files (even binary) into a python module.
To demostrate (and test :-)) the library I will use the filecmp.dircmp class.
I import filecmp and other useful modules:

    >>> import filecmp, os, shutil
    >>> from os.path import join

Command line - Usage
============================
make_res is the command line tool to prepare a resource file. Follow the syntax:

    make_res directory modulename [options]

Options:

    * -u unpack modulename in directory (default pack)
    * -r pack the whole directory tree

This options take as argument a comma separated list of wildchars (valid only for packing)

    * -i include the files matching the wildchars
    * -e exclude the files matching the wildchars
    * -d include the directories matching the wildchars
    * -x exclude the directories matching the wildchars
    
Examples:

    * make_res icons icons_res -i \*.ico

Pack in icons_res.py all \*.ico files in the directory icons

    * make_res languages languages_res -r -e \*.avi,\*.mpg

Pack in languages_res.py all the tree in languages except files with avi and mpg extension

    * make_res icons icons_res -u

Unpack all the icons in the icons folder

API
============================
The module provide the following function:
    * pack(directory,res_file,include_files,exclude_files,include_dirs,exclude_dirs,recurse)
    * unpack(directory,mod_name)
    * read_res(virtualPath)
    * open_res(virtualPath)

Now suppose we have the directory tree "res_test". I can embed the whole directory tree in a python module using. I filter out .svn directory.

    >>> from resource_packer import pack, unpack, read_res, open_res
    >>> source_directory = join('test','res_test')
    >>> destination_directory = join('test','res_test2')
    >>> pack(source_directory,"resource",
    ...      include_files=["*"],
    ...      exclude_dirs=[".svn"],
    ...      recurse=True)

I can unpack the module in a new directory tree:

    >>> unpack(destination_directory,"resource")

Now check if something is different

    >>> cmp = filecmp.dircmp(source_directory, destination_directory)
    >>> print sorted(cmp.common_files)
    ['hello', 'hello2.txt']
    >>> print sorted(cmp.common_dirs)
    ['binary']
    >>> cmp = filecmp.dircmp(join(source_directory,"binary"), join(destination_directory,"binary"))
    >>> print sorted(cmp.common_files)
    ['logo.jpg']

The module offers two useful function which are designed to read resource file. read_res returns the file content in a string. open_res return an "in-memory" file. 
These functions have one argument: a virtualpath composed by modulename:/path/to/file (corresponding to the directory/path/to/file original resource).

    >>> original_file = open(join(source_directory,"binary","logo.jpg")).read()
    >>> resource_string = read_res("resource:/binary/logo.jpg")
    >>> resource_file = open_res("resource:/binary/logo.jpg")
    >>> print original_file == resource_string == resource_file.read()
    True

Let's do some cleaning . . . 

    >>> shutil.rmtree(destination_directory)
    >>> os.remove("resource.py")
    >>> os.remove("resource.pyc")

Ok, now I want only txt files and I don't want to walk across the entire directory tree . . .

    >>> pack(source_directory,"resource",include_files=["*.txt"],recurse=False)

Unpack to a new directory tree

    >>> unpack(destination_directory,"resource")

The usual check

    >>> cmp = filecmp.dircmp(source_directory, destination_directory)
    >>> print sorted(cmp.common_files)
    ['hello2.txt']
    >>> print sorted(cmp.common_dirs)
    []

Let's do some cleaning . . . 

    >>> shutil.rmtree(destination_directory)
    >>> os.remove("resource.py")
    >>> os.remove("resource.pyc")

I hope it is useful ! 
"""
from __future__ import with_statement
import sys
from os.path import isdir, isfile, join, splitext
from os import listdir, makedirs
from fnmatch import fnmatch
import StringIO
import pprint

def _match_list(item,include,exclude):
    for e in exclude:
        if fnmatch(item,e): return False 
    for i in include:
        if fnmatch(item,i): return True
    return False
    
def _dir_to_dict(path,include_files,exclude_files,include_dirs,exclude_dirs,recurse=False):
    out = {}
    for item in listdir(path):
        if isfile(join(path,item)):
            if not _match_list(item,include_files,exclude_files):
                continue
            with open(join(path,item),"rb") as f:
                out[item]=f.read()
        elif isdir(join(path,item)):
            if not _match_list(item,include_dirs,exclude_dirs):
                continue
            if recurse:
                out[item]=_dir_to_dict(join(path,item),include_files,exclude_files,include_dirs,exclude_dirs,recurse)
    return out
    
def _dict_to_path(d,path):
    if not isdir(path): makedirs(path)
    for k,v in d.iteritems():
        if isinstance(v,dict):
            _dict_to_path(v,join(path,k))
        else:
            with open(join(path,k),"wb") as f:
                f.write(v)
            
def _dict_to_file(d,filename):
    pp=pprint.PrettyPrinter(indent=4)
    stuff = pp.pformat(d)
    with open("%s.py" % filename,"w") as f:
        f.write("data = \\\n")
        f.write(stuff)

def pack(directory,res_file,include_files=["*"],exclude_files=[],include_dirs=["*"],exclude_dirs=[],recurse=False):
    """pack files and directories (optional inclusion, exclusion, recursion)"""
    d = _dir_to_dict(directory,include_files,exclude_files,include_dirs,exclude_dirs,recurse)
    _dict_to_file(d,res_file)

def unpack(directory,mod_name):
    """unpack embedded resources in mod_name to directory"""
    mod = __import__(mod_name)
    d = mod.data
    _dict_to_path(d,directory)
    del sys.modules[mod_name]
    del mod

def read_res(virtualPath):
    """modname:/path/to/file return a string"""
    mod_name, keys = virtualPath.split(":/")
    mod = __import__(mod_name)
    d = mod.data
    virtualDirs=keys.split("/")
    for subd in virtualDirs:
        d = d[subd]
    del sys.modules[mod_name]
    del mod
    return d
    
def open_res(virtualPath):
    """modname:/path/to/file return a file"""
    d = read_res(virtualPath)
    return StringIO.StringIO(d)
